"""
@File    : pil_backend.py.py
@Time    : 2025/7/15 下午10:41
@Author  : xiashuobad
@Desc    : 

/**
 * _ooOoo_
 * o8888888o
 * 88" . "88
 * (| -_- |)
 *  O\ = /O
 * ___/`---'\____
 * .   ' \\| |// `.
 * / \\||| : |||// \
 * / _||||| -:- |||||- \
 * | | \\\ - /// | |
 * | \_| ''\---/'' | |
 * \ .-\__ `-` ___/-. /
 * ___`. .' /--.--\ `. . __
 * ."" '< `.___\_<|>_/___.' >'"".
 * | | : `- \`.;`\ _ /`;.`/ - ` : | |
 * \ \ `-. \_ __\ /__ _/ .-` / /
 * ======`-.____`-.___\_____/___.-`____.-'======
 * `=---=' bug泛滥 佛已瘫痪
"""
# rsio/core/backends/pil_backend.py
import os
import numpy as np
import logging
from typing import Literal, Union, IO

from .cv2_backend import OpencvImage, ImageBackend
from ..utils import (catch_exception, require_python_library, require_attr_not_empty, type_check, PaletteConverter,
                     ArrayShapeConverter)
from ..utils.exceptions import *


class PILImage(OpencvImage, backend_name='pil'):
    open_mode = Literal[
        'r', '1', 'CMYK', 'F', 'HSV', 'I', 'I;16', 'I;16B',
        'I;16L', 'I;16N', 'L', 'LA', 'La', 'LAB', 'P', 'PA',
        'RGB', 'RGBA', 'RGBa', 'RGBX', 'YCbCr'
    ]

    def __init__(self, file_path: str, mode: open_mode = 'r', **kwargs):
        self._img_data = None
        super().__init__(file_path, mode, **kwargs)

    @property
    @require_attr_not_empty('img_data')
    def img_array(self) -> np.ndarray:
        return np.asarray(self.img_data)

    @img_array.setter
    def img_array(self, img_array: np.ndarray):
        from PIL import Image
        with catch_exception(ImageProcessError('设置 img_array 出错！')):
            img_array = ArrayShapeConverter.array_shape_to_pil(img_array)
            self._img_data = Image.fromarray(img_array, None if self.mode == 'r' else self.mode)

    @property
    def img_data(self) -> 'PIL.Image.Image':
        return self._img_data

    @img_data.setter
    def img_data(self, img_data: 'PIL.Image.Image'):
        from PIL.Image import Image
        with catch_exception(ImageProcessError('设置 img_data 出错！')):
            if not isinstance(img_data, Image):
                raise ValueError(f'img_data必须是:PIL.Image.Image对象！')
            self._img_data = img_data
            if self.mode != 'r':
                self._img_data = self.img_data.convert(self.mode)

    def set_img_data(self, img_data: 'PIL.Image.Image') -> 'PILImage':
        self.img_data = img_data
        return self

    @property
    @require_attr_not_empty('img_data')
    def palette(self) -> list:

        return self.img_data.getpalette()

    @palette.setter
    @require_attr_not_empty('img_data')
    @type_check
    def palette(self, palette: Union[list, tuple, dict, bytes, np.ndarray]):
        with catch_exception(ImageProcessError(
                '''
                    palette调色板仅在mode='L' 或者 'P'时有效！
                    标准格式为列表：[r,g,b,r,g,b...]，
                    每3个元素表示一个颜色，每个颜色对应一个灰度值，依次为0，1，2...255！''')):
            self.img_data.putpalette(PaletteConverter.convert('pil', palette))

    def set_palette(self, palette: Union[list, tuple, dict, bytes, np.ndarray]) -> 'PILImage':
        self.palette = palette
        return self

    @require_python_library('PIL', 'pillow')
    def _checked_mode(self, mode: open_mode) -> open_mode:
        from PIL.Image import MODES
        if mode != 'r' and mode not in MODES:
            raise ImageModeError(f'不支持的打开模式：{mode}, 请使用以下模式："r" 或 {MODES}！')
        return mode

    def _open(self):
        from PIL import Image
        Image.MAX_IMAGE_PIXELS = None

        if self.mode == 'r':
            with catch_exception(ImageOpenError(f'Image.open 从文件读取图片失败！')):
                self._img_data: Image.Image = Image.open(self.file_path, self.mode, **self.kwargs)

    @require_attr_not_empty('img_data')
    @ImageBackend.require_write_mode
    def save(self, save_path: str = None, **meta):
        with catch_exception(ImageWriteError(f'PIL 保存图片失败！')):
            save_path = save_path or self.file_path
            os.makedirs(os.path.dirname(save_path), exist_ok=True)
            self.kwargs.update(meta)
            self.img_data.save(save_path, **self.kwargs)

    @require_attr_not_empty('img_data')
    @type_check
    def crop(self,
             x_off: int = 0,
             y_off: int = 0,
             window_x_size: int = None,
             window_y_size: int = None
             ) -> 'OpencvImage':
        palette = self.palette
        super().crop(x_off, y_off, window_x_size, window_y_size)
        palette and self.set_palette(palette)
        return self

    @require_attr_not_empty('img_data')
    @type_check
    def resize(self,
               target_size: tuple[int, int] | int,
               keep_ratio: bool = False,
               pad: int | tuple[int, ...] | str = None,
               resample: int = None,
               box: list[int, int, int, int] | tuple[int, int, int, int] = None,
               **kwargs
               ) -> 'PILImage':
        with catch_exception(ImageProcessError('pil对图像进行resize操作失败')):
            if isinstance(target_size, int):
                target_size = (target_size, target_size)
            if not keep_ratio:
                self._img_data = self.img_data.resize(target_size, resample, box, **kwargs)
                return self

            target_width, target_height = target_size
            original_width, original_height = self.size
            # 计算缩放比例
            scale = min(target_width / original_width, target_height / original_height)
            # 计算新的尺寸
            new_width = int(original_width * scale)
            new_height = int(original_height * scale)
            # 缩放图像
            self._img_data = self.img_data.resize((new_width, new_height), resample, box, **kwargs)
            if pad:
                from PIL import Image
                new_img = Image.new(self.img_data.mode, (target_width, target_height), pad)
                # 计算粘贴位置（居中）
                paste_x = (target_width - new_width) // 2
                paste_y = (target_height - new_height) // 2

                # 粘贴缩放后的图像
                new_img.paste(self.img_data, (paste_x, paste_y))
                self._img_data = new_img

            return self

    @staticmethod
    @require_python_library('PIL', 'pillow')
    @type_check
    def write_array(
            fp: Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] | IO[bytes],
            array: np.ndarray,
            mode: str = None,
            palette: Union[list, tuple, dict, bytes, np.ndarray] = None,
            file_format: str = None,
            **params: dict
    ):
        from PIL import Image
        with catch_exception(ImageWriteError('pil 写入图片失败！')):
            array = ArrayShapeConverter.array_shape_to_pil(array)
            image = Image.fromarray(array, mode)
            palette and mode == 'L' and image.putpalette(PaletteConverter.convert('pil', palette))
            image.save(fp, file_format, **params)

    @staticmethod
    @require_python_library('PIL', 'pillow')
    @type_check
    def show_image(img_array: np.ndarray, title: str = None):
        from PIL import Image
        with catch_exception(ImageShowError('pil 显示图片失败！')):
            img_array = ArrayShapeConverter.array_shape_to_pil(img_array)
            image = Image.fromarray(img_array)
            image.show(title)

    @require_attr_not_empty('img_data')
    def show(self, title: str = None):
        self.img_data.show(title or self.file_name)

    def close(self):
        self.img_data is not None and self.img_data.close()

    @require_attr_not_empty('img_data')
    def __getattr__(self, attr):
        return getattr(self.img_data, attr)
